<body>
  <video id="player" controls></video>
  <input type="file" id="uploader" />
  <script src="/asset/mux.min.js"></script>
  <script src="/asset/ffmpeg.min.js"></script>
  <script src="/asset/rxjs.umd.js"></script>
  <script>
    const { Observable, fromEvent, combineLatest, zip } = rxjs;
    const { map, flatMap, take, skip } = rxjs.operators;
    const { createFFmpeg, fetchFile } = FFmpeg;
    const bufferStream = files =>
      new Observable(async subscriber => {
        const ffmpeg = createFFmpeg({
          corePath: '/asset/ffmpeg-core.js',
          log: true
        });

        const fileExists = file => ffmpeg.FS('readdir', '/').includes(file);
        const readFile = file => ffmpeg.FS('readFile', file);

        await ffmpeg.load();
        const { name } = files[0];
        ffmpeg.FS('writeFile', name, await fetchFile(files[0]));

        let index = 0;

        ffmpeg
          .run(
            '-i',
            'input.mp4',
            '-g',
            '1',
            // Encode for MediaStream
            '-segment_format_options',
            'movflags=frag_keyframe+empty_moov+default_base_moof',
            // encode 5 second segments
            '-segment_time',
            '5',
            // write to files by index
            '-f',
            'segment',
            '%d.mp4'
          )
          .then(() => {
            // send out the remaining files
            while (fileExists(`${index}.mp4`)) {
              subscriber.next(readFile(`${index}.mp4`));
              index++;
            }
            subscriber.complete();
          });

        setInterval(() => {
          // periodically check for files that have been written
          if (fileExists(`${index + 1}.mp4`)) {
            subscriber.next(readFile(`${index}.mp4`));
            index++;
          }
        }, 200);
      });

    const transcode = async ({ target: { files } }) => {
      let t1 = Date.now();

      const videoPlayer = document.getElementById('player');
      const mediaSource = new MediaSource();
      videoPlayer.src = URL.createObjectURL(mediaSource);
      videoPlayer.play();

      const mediaSourceOpen = fromEvent(mediaSource, 'sourceopen');

      const bufferStreamReady = combineLatest(mediaSourceOpen, bufferStream(files)).pipe(map(([, a]) => a));

      const sourceBufferUpdateEnd = bufferStreamReady.pipe(
        take(1),
        map(buffer => {
          // create a buffer using the correct mime type
          const mime = `video/mp4; codecs="${muxjs.mp4.probe
            .tracks(buffer)
            .map(t => t.codec)
            .join(',')}"`;
          const sourceBuf = mediaSource.addSourceBuffer(mime);

          // append the buffer
          mediaSource.duration = 5;
          sourceBuf.timestampOffset = 0;
          sourceBuf.appendBuffer(buffer);

          // create a new event stream
          return fromEvent(sourceBuf, 'updateend').pipe(map(() => sourceBuf));
        }),
        flatMap(value => value)
      );

      zip(sourceBufferUpdateEnd, bufferStreamReady.pipe(skip(1)))
        .pipe(
          map(([sourceBuf, buffer]) => {
            mediaSource.duration += 5;
            sourceBuf.timestampOffset += 5;
            sourceBuf.appendBuffer(buffer.buffer);
          })
        )
        .subscribe();

      console.log(Date.now() - t1);
    };
    document.getElementById('uploader').addEventListener('change', transcode);
  </script>
</body>
